package fr.lelouet.monitoring.impl;

import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

import fr.lelouet.monitoring.DataHandler;
import fr.lelouet.monitoring.UsageMonitor;
import fr.lelouet.monitoring.perf.Tools;


/** a monitor for an existing process's performances.<br />
 * Starts process and thread for controlling them ; it has to be controlled by another daemon
 */
class ProcessMonitor implements UsageMonitor{
	
	public final static String MONITORING_DURATION_S_KEY="monitor.durations"
	
	public static final String MONITOREDEVENTS_KEY="monitor.events"
	
	private final static Logger logger = LoggerFactory.getLogger(ProcessMonitor.class)
	
	/** the pid of the process we want to monitor */
	int pid
	
	/** the id of the hypervisor the monitored process is running on*/
	String hvId
	
	/** creates a KvmMonitor that monitors given process on given events*/
	public ProcessMonitor(int pid, String hypervisorId, List<String> events) {
		this.pid = pid
		this.hvId=hypervisorId
		setEvents(events)
	}
	
	public ProcessMonitor(int pid, String hypervisorId) {
		this(pid, hypervisorId, ["simple"])
	}
	
	protected final Object accessLock = new Object()
	
	protected Collection<String> events
	
	
	
	/** number of seconds during witch we monitor the process*/
	protected float monitoring_duration_s = 1
	
	public void setMonitoringDuration(float secs) {
		if(secs>0) {
			monitoring_duration_s =secs
			bashCommand=null
		}
	}
	
	protected String bashCommand=null
	
	public String getBashCommand() {
		String ret=bashCommand
		while(ret==null && !interrupted) {
			generateCommand()
			ret=bashCommand
		}
		return ret
	}
	
	public static final String ALLEVENTS_TOKEN="all"
	public static final String SIMPLEEVENTS_TOKEN="simple"
	
	/** generates the command run to monitor informations*/
	protected void generateCommand() {
		String ret = "perf stat -p ${pid} 2>&1"
		events.each {
			if(Tools.goodEvent(it)) {
				ret+=" -e "+it
			} else {
				logger.error("event <$it> not allowed, allowed events are : "+Tools.getAllowedEvents())
			}
		}
		ret+="& sleep ${monitoring_duration_s}; kill -2 \${!}"
		bashCommand = ret
		logger.info "generated command for ProcessMonitor {} : {}", pid, bashCommand
	}
	
	/** replaces the list of events to check on the process*/
	public void setEvents(Collection<String> events) {
		if (events.find {ALLEVENTS_TOKEN.equals(it) }) {
			this.events = Tools.getAllowedEvents()
		} else if(events.find {SIMPLEEVENTS_TOKEN.equals(it) }) {
			this.events = Tools.getSimpleEvents()
		} else {
			this.events = events.findAll {it.size()>0 }
		}
		bashCommand=null
	}
	
	/** if set to true, any loop should stop now*/
	protected boolean interrupted = false
	
	final Runnable monitorRun = 
		{
			try {
				String command = getBashCommand()
				synchronized(accessLock) {
					Process proc = [
						"bash",
						"-c",
						command
					].execute()
					//					logger.info("new process, input={}, output={}, error={}", proc.inputStream, proc.outputStream, ((FileInputStream) proc.errorStream).getChannel()  )
					try {
						proc.waitFor()
					} catch(Exception e) {
						logger.warn(e.toString())
					}
					String data =proc.inputStream.getText()
					String err = proc.errorStream.getText()
					proc.errorStream.close()
					proc.inputStream.close()
					proc.outputStream.close()
					if(proc.exitValue()!=0) {
						if(data.contains("not found")) {
							logger.warn("perf tool not installed. you can install it on debian by typing \"apt-get install linux-tools\"")
						}
						else if(data.contains("Can't find all threads of pid")){
							logger.info("cannot monitor vm {}, pid not found by the OS", pid)
						}
						else {
							logger.info " command : "+command+ "\ndata : " + data+ "\nerror : "+err
						}
					} else {
						Map map = Tools.convertString(data)
						Tools.timeMap map
						Tools.markHypervisor map, hvId
						handlers.each { it.handle(map) }
					}
				}
			} catch(Exception e) {
				logger.warn("{} @ {}", e, e.getStackTrace()[0] )
			}
			gatherSuccessful = true
		}
	
	
	
	Thread monitorThread 
	
	/** start a thread to monitor the observed process<br />
	 * The result will be sent to registered handlers*/
	public void gatherInformations() {
		gatherSuccessful = false
		interrupted=false
		monitorThread = new Thread(monitorRun)
		monitorThread.start()
	}
	
	protected List<DataHandler> handlers = []
	
	/** modify the list of handlers this uses*/
	public void setHandlers(List<DataHandler> handlers) {
		this.handlers=handlers
	}
	
	public void configure(Properties prop) {
		try {
			float duration = Float.parseFloat(prop.getProperty(MONITORING_DURATION_S_KEY, "1"))
			if(duration>0) {
				monitoring_duration_s=duration
			} else {
				monitoring_duration_s
				logger.warn("bad float value for $MONITORING_DURATION_S_KEY : $duration_s")
			}
		} catch(Exception e) {
			logger.error(e.toString()+e.getStackTrace())
		}
		setEvents(prop.getProperty(ProcessMonitor.MONITOREDEVENTS_KEY, "simple").split(" ") as List)
	}
	
	List<String>  monitoredEvents=null
	public List<String> getMonitoredEvents() {
		if(monitoredEvents==null) {
			String monitoredEventsProperty=props.getProperty(MONITOREDEVENTS_KEY, "")
			monitoredEvents=[]
			monitoredEventsProperty.split(" ").each{monitoredEvents.add(it) }
		}
		return monitoredEvents
	}
	
	public void interrupt() {
		if(monitorThread!=null) {
			interrupted=true
			monitorThread.interrupt()
		}
	}
	
	protected boolean gatherSuccessful = true
	public boolean informationGathered() {
		return gatherSuccessful
	}
}